import {loadWasmBinary} from "./wasm/opus_helper";
import {base64DecToArr} from "./utils/utils";
import {buildOpusEventInd, buildOpusEventReq, OPUS_EVENT} from "./wasm/opus_event";
import {WLRecorder} from "./recorder/recorder_core";
import {WLPlayer} from "./player/player_core";

// @ts-ignore
import OpusWorker from "workerize-loader?inline!./wasm/opus_worker"

interface OPUS_WORKER_CALLBACK {
    resolve: (value:any) => any;
    reject: (reason: any) => void;
    data?: any;
}

enum PLAYER_ID {
    SINGLE_PLAYER,
    PTT_PLAYER
}

class WLAudioCore {
    private audioContext: any;
    private coderWorker: Worker | undefined;
    private opusWorkerCbList: Map<number, OPUS_WORKER_CALLBACK>;
    private recorderCore: WLRecorder | undefined;
    private readonly players: WLPlayer[];
    private codedDataCallback: ((data: Uint8Array) => void) | undefined;
    private playEndCallback: ((id: number) => void) | undefined;

    constructor() {
        this.opusWorkerCbList = new Map<number, OPUS_WORKER_CALLBACK>();
        this.players = [];
    }

    public setCodedDataReceiveCb(cb: ((data: Uint8Array) => void) | undefined) {
        this.codedDataCallback = cb;
    }

    public setPlayerEndCb(cb: ((id: number) => void) | undefined) {
        this.playEndCallback = cb;
    }

    public initAudio(sampleRate: number): boolean {
        if (this.isAudioSupport()) {
            let AC = window.AudioContext;
            if (!AC) {
                // @ts-ignore
                AC = window.webkitAudioContext;
            }

            if (!this.audioContext || this.audioContext.state == "closed") {
                console.log("initAudio.....sampleRate:", sampleRate);
                this.audioContext = new AC({sampleRate: sampleRate});
            }

            if (!this.audioContext) {
                return false;
            }

            if (this.audioContext.state != 'running') {
                this.audioContext.resume();
            }

            this.recorderCore = new WLRecorder(sampleRate);

            this.initCoderWorker().then(value => {
                console.log("初始化Opus Worker成功");
            }).catch(reason => {
                console.log("初始化Opus Worker失败");
            });

            this.players[PLAYER_ID.SINGLE_PLAYER] = new WLPlayer(this.audioContext, 16000, PLAYER_ID.SINGLE_PLAYER);
            this.players[PLAYER_ID.PTT_PLAYER] = new WLPlayer(this.audioContext, 16000, PLAYER_ID.PTT_PLAYER);

            return true;
        }

        return false;
    }

    private isAudioSupport(): boolean {
        let AC = window.AudioContext;
        console.log("window.AudioContext 支持否?", AC);
        if (!AC) {
            // @ts-ignore
            AC = window.webkitAudioContext;
            console.log("window.webkitAudioContext 支持否?", AC);
        }

        if (!AC) {
            return false;
        }

        let scope: any = navigator.mediaDevices || {};
        if (!scope.getUserMedia) {
            scope = navigator;
            scope.getUserMedia ||
            (scope.getUserMedia = scope.webkitGetUserMedia || scope.mozGetUserMedia || scope.msGetUserMedia);
        }

        if (!scope.getUserMedia) {
            return false;
        }

        return true;
    }

    private async initCoderWorker(): Promise<boolean> {
        return new Promise<boolean>((resolve, reject) => {
            if (!this.coderWorker) {
                let wasmBase64 = localStorage.getItem("WASMB");
                if (!wasmBase64) {
                    loadWasmBinary();
                    wasmBase64 = localStorage.getItem("WASMB");
                }

                // this.coderWorker = new Worker(new URL("./wasm/opus_worker.js", import.meta.url));
                this.coderWorker = new OpusWorker();
                this.coderWorker!.onmessage = this.onCoderMessage.bind(this);
                const wasmBuffer = base64DecToArr(wasmBase64!);
                const req = buildOpusEventReq(OPUS_EVENT.OPUS_INIT_WORKER_EVT, {wasmData: wasmBuffer})
                this.opusWorkerCbList.set(OPUS_EVENT.OPUS_INIT_WORKER_EVT, {
                    resolve: resolve,
                    reject: reject
                });
                this.coderWorker!.postMessage(req);
            }
        })
    }

    private onCoderMessage(evt: MessageEvent): void {
        const event = evt.data.event & 0xFF00;
        const data = evt.data.data;
        const cb = this.opusWorkerCbList.get(event);
        switch (event) {
            case OPUS_EVENT.OPUS_INIT_WORKER_EVT: {
                console.log("OPUS_INIT_WORKER_EVT");
            }
            break;

            case OPUS_EVENT.OPUS_START_ENCODE_EVT: {
                console.log("OPUS_START_ENCODE_EVT");
            }
            break;

            case OPUS_EVENT.OPUS_STOP_ENCODE_EVT: {
                console.log("OPUS_STOP_ENCODE_EVT");
            }
            break;

            case OPUS_EVENT.OPUS_ENCODE_DATA_EVT: {
                if (this.codedDataCallback) {
                    this.codedDataCallback(data.data);
                }
            }
            break;
        }

        if (cb) {
            this.opusWorkerCbList.delete(event && 0xFF00);
            if (data.errCode == 0) {
                if (cb && cb.resolve) {
                    cb.resolve(true)
                }
            }else {
                if (cb && cb.reject) {
                    cb.reject(data.errMsg);
                }
            }
        }

    }

    public async startRecorder(errorCb: (error: string) => void): Promise<boolean> {
        return new Promise<boolean>(async (resolve, reject) => {
            if (this.recorderCore) {
                this.recorderCore.setRecorderDataCb((data) => {
                    if (this.coderWorker) {
                        const ind = buildOpusEventInd(OPUS_EVENT.OPUS_ENCODE_DATA_EVT, {
                            data: data
                        });
                        this.coderWorker.postMessage(ind);
                    }
                })

                this.recorderCore.setErrorCb(errorCb);

                await this.recorderCore.open();
                await (async () => new Promise<boolean>((resolve1, reject1) => {
                    if (this.coderWorker) {
                        const req = buildOpusEventReq(OPUS_EVENT.OPUS_START_ENCODE_EVT, {
                            sampleRate: this.recorderCore!.getRecorderDataSamplerate(), bitRate: 16*1024
                        });
                        this.opusWorkerCbList.set(OPUS_EVENT.OPUS_START_ENCODE_EVT, {
                            resolve: resolve1, reject: reject1});
                        this.coderWorker.postMessage(req);
                        return;
                    }

                    reject1("WORKER Not ready");
                }))();

                console.log("准备打开录音")
                await this.recorderCore.start();
                resolve(true);
            }
        })
    }

    public async stopRecorder(): Promise<boolean> {
        return new Promise<boolean>(async (resolve, reject) => {
            await (async () => new Promise<boolean>((resolve1, reject1) => {
                if (this.coderWorker) {
                    const req = buildOpusEventReq(OPUS_EVENT.OPUS_STOP_ENCODE_EVT);
                    this.opusWorkerCbList.set(OPUS_EVENT.OPUS_STOP_ENCODE_EVT, {
                        resolve: resolve1,
                        reject: reject1
                    });
                    this.coderWorker.postMessage(req);
                }
            }));

            if (this.recorderCore) {
                await this.recorderCore.stop();
                await this.recorderCore.close();
            }

            resolve(true);
        })
    }

    public async startPlayer(id: number): Promise<boolean> {
        return new Promise<boolean>(async (resolve, reject) => {
            console.log("startPlayer:", id, this.players);
            if (id >=0 && id < this.players.length) {
                this.players[id].onPlayEndListener((id: number) => {
                    if (this.playEndCallback) {
                        this.playEndCallback(id);
                    }
                })
                await this.players[id].open();
                console.log("open succ");
                await this.players[id].start();

                resolve(true);
                return;
            }

            reject("播放器ID出错");
        })
    }

    public feedPlayerCodedData(id: number, u8DataList: Uint8Array[]|undefined) {
        if (id >=0 && id < this.players.length) {
            if (u8DataList) {
                this.players[id].feedData(u8DataList, false);
            }else {
                this.players[id].feedData(u8DataList, true);
            }
        }
    }

    public async stopPlayer(id: number): Promise<boolean> {
        return new Promise<boolean>(async (resolve, reject) => {
            if (id >=0 && id < this.players.length) {
                await this.players[id].stop();
                await this.players[id].close();
                resolve(true);
                return;
            }

            reject("播放器ID出错");
        })
    }

    public async pausePlayer(id: number): Promise<boolean> {
        return new Promise<boolean>(async (resolve, reject) => {
            if (id >=0 && id < this.players.length) {
                await this.players[id].pause();
                resolve(true);
                return;
            }

            reject("播放器ID出错");
        })
    }

    public async resumePlayer(id: number): Promise<boolean> {
        return new Promise<boolean>(async (resolve, reject) => {
            if (id >=0 && id < this.players.length) {
                await this.players[id].resume();
                resolve(true);
                return;
            }

            reject("播放器ID出错");
        })
    }
}

export {WLAudioCore};
